﻿using ff14bot;
using ff14bot.Behavior;
using ff14bot.Helpers;
using ff14bot.Managers;
using ff14bot.Navigation;
using ff14bot.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using TreeSharp;
using Action = TreeSharp.Action;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Magitek.Helpers
{
    public static class Extensions
    {
        [DllImport("user32.dll")]
        public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
        public static bool Resting = false;

        #region Need to find a better way.
        public static Composite AcceptRaise
        {
            get
            {
                return new Decorator(r => Core.Player.IsDead && Magitek.WindowSettings.acceptraise, new Sequence(
                    new Action(r => 
                    {

                        const uint WM_KEYDOWN = 0x100;
                        const uint WM_KEYUP = 0x0101;


                        PostMessage(Core.Memory.Process.MainWindowHandle, WM_KEYDOWN, (IntPtr) (Keys.NumPad0), IntPtr.Zero);
                        PostMessage(Core.Memory.Process.MainWindowHandle, WM_KEYUP, (IntPtr) (Keys.NumPad0), IntPtr.Zero);
                        PostMessage(Core.Memory.Process.MainWindowHandle, WM_KEYDOWN, (IntPtr) (Keys.NumPad0), IntPtr.Zero);
                        PostMessage(Core.Memory.Process.MainWindowHandle, WM_KEYUP, (IntPtr) (Keys.NumPad0), IntPtr.Zero);
                        PostMessage(Core.Memory.Process.MainWindowHandle, WM_KEYDOWN, (IntPtr) (Keys.Escape), IntPtr.Zero);
                        PostMessage(Core.Memory.Process.MainWindowHandle, WM_KEYUP, (IntPtr) (Keys.Escape), IntPtr.Zero);


                        Logging.Write("[Magitek] Accepting Raise");

                    }),
                    new Sleep(1000)));
            }

        }
        #endregion
        #region Mastagh's Kupo code.
        public delegate T Selection<out T>(object context);
        public static bool HasAura(this GameObject unit, string spellname, bool isMyAura = false, int msLeft = 0)
        {
            var unitasc = (unit as Character);
            if (unit == null || unitasc == null)
            {
                return false;
            }
            IEnumerable<Aura> auras;
            auras = isMyAura ? unitasc.CharacterAuras.Where(r => r.CasterId == Core.Player.ObjectId && r.Name == spellname) : unitasc.CharacterAuras.Where(r => r.Name == spellname);


            return auras.Any(aura => aura.TimespanLeft.TotalMilliseconds > msLeft);
        }

        #region Double cast dictionary code from Mastagh
        public static void RemoveAll<TKey, TValue>(this Dictionary<TKey, TValue> dic,
        Func<TValue, bool> predicate)
        {
            var keys = dic.Keys.Where(k => predicate(dic[k])).ToList();
            foreach (var key in keys)
            {
                dic.Remove(key);
            }
        }

        public static void UpdateDoubleCastDict(string spellName, GameObject unit)
        {
            if (unit == null)
                return;

            SpellData spellData;


            Actionmanager.CurrentActions.TryGetValue(spellName, out spellData);


            if (spellData == null)
                return;



            DateTime expir = DateTime.UtcNow + spellData.AdjustedCastTime + TimeSpan.FromSeconds(3);
            string key = DoubleCastKey(unit.ObjectId, spellName);
            if (DoubleCastPreventionDict.ContainsKey(key))
                DoubleCastPreventionDict[key] = expir;

            DoubleCastPreventionDict.Add(key, expir);
        }

        public static string DoubleCastKey(uint guid, string spellName)
        {
            return guid.ToString("X") + "-" + spellName;
        }

        public static string DoubleCastKey(GameObject unit, string spell)
        {
            return DoubleCastKey(unit.ObjectId, spell);
        }

        public static bool Contains(this Dictionary<string, DateTime> dict, GameObject unit, string spellName)
        {
            return dict.ContainsKey(DoubleCastKey(unit, spellName));
        }

        public static bool ContainsAny(this Dictionary<string, DateTime> dict, GameObject unit, params string[] spellNames)
        {
            return spellNames.Any(s => dict.ContainsKey(DoubleCastKey(unit, s)));
        }

        public static bool ContainsAll(this Dictionary<string, DateTime> dict, GameObject unit, params string[] spellNames)
        {
            return spellNames.All(s => dict.ContainsKey(DoubleCastKey(unit, s)));
        }

        public static readonly Dictionary<string, DateTime> DoubleCastPreventionDict = new Dictionary<string, DateTime>();

        #endregion
        #region Healing double cast code
        public static void HealRemoveAll<TKey, TValue>(this Dictionary<TKey, TValue> dic,
        Func<TValue, bool> predicate)
        {
            var keys = dic.Keys.Where(k => predicate(dic[k])).ToList();
            foreach (var key in keys)
            {
                dic.Remove(key);
            }
        }

        public static void HealUpdateDoubleCastDict(string spellName, GameObject unit)
        {
            if (unit == null)
                return;

            SpellData spellData;


            Actionmanager.CurrentActions.TryGetValue(spellName, out spellData);


            if (spellData == null)
                return;



            DateTime expir = DateTime.UtcNow + spellData.AdjustedCastTime + TimeSpan.FromSeconds(Magitek.WindowSettings.HealDoubleCastingTimer);
            string key = HealDoubleCastKey(unit.ObjectId, spellName);
            if (HealDoubleCastPreventionDict.ContainsKey(key))
                HealDoubleCastPreventionDict[key] = expir;

            HealDoubleCastPreventionDict.Add(key, expir);
        }

        public static string HealDoubleCastKey(uint guid, string spellName)
        {
            return guid.ToString("X") + "-" + spellName;
        }

        public static string HealDoubleCastKey(GameObject unit, string spell)
        {
            return HealDoubleCastKey(unit.ObjectId, spell);
        }

        public static bool HealContains(this Dictionary<string, DateTime> dict, GameObject unit, string spellName)
        {
            return dict.ContainsKey(HealDoubleCastKey(unit, spellName));
        }

        public static bool HealContainsAny(this Dictionary<string, DateTime> dict, GameObject unit, params string[] spellNames)
        {
            return spellNames.Any(s => dict.ContainsKey(HealDoubleCastKey(unit, s)));
        }

        public static bool HealContainsAll(this Dictionary<string, DateTime> dict, GameObject unit, params string[] spellNames)
        {
            return spellNames.All(s => dict.ContainsKey(HealDoubleCastKey(unit, s)));
        }

        public static readonly Dictionary<string, DateTime> HealDoubleCastPreventionDict = new Dictionary<string, DateTime>();

        #endregion

        public static Composite EnsureTarget
        {
            get
            {
                return new Decorator(r => Core.Player.SpellCastInfo != null && Core.Player.SpellCastInfo.TargetId != GameObjectManager.EmptyGameObject,
                    new PrioritySelector(
                        r => GameObjectManager.GetObjectByObjectId(Core.Player.SpellCastInfo.TargetId),
                        new Decorator(r => (r as Character) != null && (r as Character).IsDead,


                            new PrioritySelector(
                                new FailLogger(r => string.Format("Stop casting at {0} because it is dead.", (r as GameObject).Name)),
                                new Action(r => Actionmanager.StopCasting()))

                    )));
            }
        }
        public static Composite DefaultRestBehavior(Selection<float> CurrentEnergy)
        {
            return new PrioritySelector(

                new Decorator(r => Resting, new PrioritySelector(
                    new FailLogger(r => "Waiting to recover our health/mana back"),
                    new Decorator(r => MovementManager.IsMoving, new Action(r => Navigator.PlayerMover.MoveStop())),
                    new Decorator(r => Core.Player.CurrentHealthPercent < Magitek.WindowSettings.RestHealthDone, new ActionAlwaysSucceed()),
                    new Decorator(r => CurrentEnergy(r) < Magitek.WindowSettings.RestEnergyDone, new ActionAlwaysSucceed()),
                    new Action(r => Resting = false)
                    )),
                new Decorator(r => CurrentEnergy(r) < Magitek.WindowSettings.RestEnergy || Core.Player.CurrentHealthPercent < Magitek.WindowSettings.RestHealth, new Action(r => Resting = true))


                );
        }
        public static BattleCharacter GetTargetMissingDebuff(GameObject near, string debuff, float distance = -1f)
        {
            return UnfriendlyUnits.FirstOrDefault(u => u.Location.Distance3D(near.Location) <= distance && !u.HasAura(debuff, true));
        }
        public static int EnemiesNearTarget(float range)
        {
            var target = Core.Player.CurrentTarget;
            if (target == null)
                return 0;
            var tarLoc = target.Location;
            return UnfriendlyUnits.Count(u => u.ObjectId != target.ObjectId && u.Location.Distance3D(tarLoc) <= range);
        }
        public static int EnemiesNearPlayer(float range)
        {
            var target = Core.Player;
            if (target == null)
                return 0;
            var tarLoc = target.Location;
            return UnfriendlyUnits.Count(u => u.ObjectId != target.ObjectId && u.Location.Distance3D(tarLoc) <= range);
        }
        public static IEnumerable<BattleCharacter> UnfriendlyUnits
        {
            get
            {
                return
                    GameObjectManager.GetObjectsOfType<BattleCharacter>().Where(u => !u.IsDead && u.CanAttack);
            }
        }
        public static Composite SummonChocobo()
        {
            return new Decorator(r => Magitek.WindowSettings.SummonChocobo && !Chocobo.Summoned && Chocobo.CanSummon,
                new Action(r =>
                {
                    if (MovementManager.IsMoving)
                        Navigator.PlayerMover.MoveStop();

                    Chocobo.Summon();
                    Thread.Sleep(1500);
                                    
                }));
        }
        #endregion
        public static Composite WaitTime(int time)
        {
            return new WaitContinue(time, r => false, new ActionAlwaysSucceed());
        }
        public static Composite PetFollow
        {
            get
            {
                return new Decorator(r => Magitek.WindowSettings.PetFollow && !Core.Player.InCombat && Core.Player.Pet != null && Pet.PetMovement != PetMovement.Heel
                    , new PrioritySelector(Casting.PetCast(r => Core.Player.Pet, r => "Heel", r => true)));
            }
        }
    }
}
